// Program: drbcount
// Author: Marta Murray-Close
// Last revised: 02/12/20

/* This program rounds any of the following according to the DRB rounding rule
for counts: (1) a number, (2) each element of a matrix, or (3) each variable 
of a variable list. */ 

/* The suppress option allows the user to specify the minimum value that will 
be rounded. Counts less than this value will be set to missing. The default is
15. */

/* The macname option is for use when rounding a number. It allows the user to 
request that the rounded number be returned in a local macro with the 
specified name rather than the default scalar r(rounded). */

/* The matname option is for use when rounding a matrix. It allows the user to
request that the rounded elements be returned in a matrix with the specified 
name rather than the default matrix r(rounded). */

/* The generate option is for use when rounding a list of variables. Either
generate or replace (but not both) must be used. When generate is used, the
program generates rounded variables newvar1, newvar2,..., newvarK corresponding 
to the K unrounded variables. */

/* The replace option is for use when rounding a list of variables. Either
generate or replace (but not both) must be used. When replace is used, the 
program replaces the unrounded variables with rounded variables. */

program define drbcount, rclass
version 13.0
syntax anything [, ///			/* anything is a number or matrix or list of variables */
	suppress(integer 15) ///	/* suppress counts less than specified integer */
	macname(name) ///			/* return rounded number in the specified local macro */
	matname(name) ///			/* return rounded matrix with the specified name */
	Generate(namelist) ///		/* generate rounded variables with the specified names */
	replace	///					/* replace unrounded variables with rounded variables */
	]

	
/* Did user enter a number or a matrix or a variable? */
/******************************************************************************/

/* Check entry type. */
capture confirm number `anything'
if _rc == 0 local entry_type number
capture confirm matrix `anything'
if _rc == 0 local entry_type matrix
capture unab variables : `anything'
capture confirm numeric variable `variables'
if _rc == 0 local entry_type variable

/* If entry type is not valid, display error message and exit. */
if !inlist("`entry_type'", "number", "matrix", "variable") {
	disp as error "argument must be a number, a matrix, or a list of numeric variables"
	exit
}


/* User entered a number. */
/******************************************************************************/

if "`entry_type'" == "number" {

	/* Round number. */
	local x = `anything'
	if `x' < `suppress' local y = .
	if `x' >= `suppress' & `x' <= 99 local y = round(`x', 10)
	if `x' >= 100 & `x' <= 999 local y = round(`x', 50)
	if `x' >= 1000 & `x' <= 9999 local y = round(`x', 100)
	if `x' >= 10000 & `x' <= 99999 local y = round(`x', 500)
	if `x' >= 100000 & `x' <= 999999 local y = round(`x', 1000)
	if `x' >= 1000000 local y = round(`x',10^(floor(log10(`x'))-3))
	
	/* If macname is used, return rounded number in specified local macro. */.
	if "`macname'" != "" c_local `macname' = `y'
	/* Return rounded number in r(rounded). */
	return scalar rounded = `y'
	
}


/* User entered a matrix. */
/******************************************************************************/

if "`entry_type'" == "matrix" {

	/* Round each element of matrix. */
	tempname m
	mat `m' = `anything'
	forval i = 1/`=rowsof(matrix(`m'))' {
	forval j = 1/`=colsof(matrix(`m'))' {
		local x = `m'[`i',`j']
		if `x' < `suppress' mat `m'[`i',`j'] = .
		if `x' >= `suppress' & `x' <= 99 mat `m'[`i',`j'] = round(`x', 10)
		if `x' >= 100 & `x' <= 999 mat `m'[`i',`j'] = round(`x', 50)
		if `x' >= 1000 & `x' <= 9999 mat `m'[`i',`j'] = round(`x', 100)
		if `x' >= 10000 & `x' <= 99999 mat `m'[`i',`j'] = round(`x', 500)
		if `x' >= 100000 & `x' <= 999999 mat `m'[`i',`j'] = round(`x', 1000)
		if `x' >= 1000000 mat `m'[`i',`j'] = round(`x',10^(floor(log10(`x'))-3))
	}
	}
	
	/* If matname is used, return rounded matrix with this name. */
	if "`matname'" != "" mat `matname' = `m'
	/* Return rounded matrix in r(rounded). */
	return matrix rounded = `m'	
	
}


/* User entered a list of variables. */
/******************************************************************************/

if "`entry_type'" == "variable" {

	/* If user did not specify either generate or replace, display error 
	message and	exit. */
	if "`replace'" == "" & "`generate'" == "" {
		disp as error "either replace or generate is required when rounding variables"
		exit
	}
	
	/* If user specified both generate and replace, display error message and
	exit. */
	if "`replace'" != "" & "`generate'" != "" {
		disp as error "generate and replace cannot be combined"
		exit
	}
	
	/* Count variables to round. */
	unab vars_to_round : `anything'
	local num_to_round : word count `vars_to_round'

	/* If generate is used, count variables to generate. Confirm that number of 
	variables to generate equals number of variables to round. */
	if "`generate'" != "" {
		local vars_to_gen `generate'
		local num_to_gen : word count `generate'
		if `num_to_gen' != `num_to_round' {
			disp as err "number of variables to generate must equal number of variables to round"
			exit
		}
	}

	/* Loop over variables to round. */
	forvalues i = 1/`num_to_round' {
	
		/* Retrieve name and format of each variable to round. */
		local var_to_round : word `i' of `vars_to_round'
		local fmt_of_var_to_round : format `var_to_round'
		
		/* If generate is used, retrieve name of corresponding variable to 
		generate. */
		if "`generate'" != "" local var_to_gen : word `i' of `vars_to_gen'
	
		/* Round each variable. */
		tempvar var_rounded
		quietly: gen `var_rounded' = .
		quietly: replace `var_rounded' = round(`var_to_round', 10) ///
			if `var_to_round' >= `suppress' & `var_to_round' <= 99
		quietly: replace `var_rounded' = round(`var_to_round', 50) ///
			if `var_to_round' >= 100 & `var_to_round' <= 999
		quietly: replace `var_rounded' = round(`var_to_round', 100) ///
			if `var_to_round' >= 1000 & `var_to_round' <= 9999
		quietly: replace `var_rounded' = round(`var_to_round', 500) ///
			if `var_to_round' >= 10000 & `var_to_round' <= 99999
		quietly: replace `var_rounded' = round(`var_to_round', 1000) ///
			if `var_to_round' >= 100000 & `var_to_round' <= 999999
		quietly: replace `var_rounded' = ///
			round(`var_to_round',10^(floor(log10(`var_to_round'))-3)) ///
			if `var_to_round' >= 1000000 & `var_to_round' < .
		
		/* If replace is used, replace unrounded variable with rounded 
		variable. */
		if "`replace'" != "" {
			quietly: replace `var_to_round' = `var_rounded'
			disp as text "values of `var_to_round' replaced with rounded values"
		}
		
		/* If generate is used, generate rounded variable with specified 
		name. Give rounded variable same format as the unrounded variable. */
		if "`generate'" != "" {
			quietly: gen `var_to_gen' = `var_rounded'
			format `var_to_gen' `fmt_of_var_to_round'
			disp as text "`var_to_gen' contains rounded values of `var_to_round'"
		}
		
		drop `var_rounded'
	
	}
			
}

end
